零. 前言
从我开始接触前端时就听说过闭包,但是一直不理解闭包究竟是什么。上网看了各种博客,大家对闭包的说法不一。闭包在我理解是一种比较抽象的东西。所以我写了一篇博文来方便自己理解闭包。博主是第一次写博文,如果在文章中有什么看不懂或者概念错误的地方,请大家多多见谅和指出错误。
一. 闭包的定义
再说闭包之前,首先让我们先来理解一下自由变量和约束变量。
在程序设计语言中,变量可以分为自由变量与约束变量两种。简单来说,一个函数里局部变量和参数都被认为是约束变量;而不是约束变量的则是自由变量。下面我们通过一个demo来解说。
var x = 10; // 相对于fn来说,x是一个自由变量
function fn(){
var b = 20;
console.log( x + b ); // 30
}
fn();
在上述例子中,相对于函数实例fn而言,x是一个自由变量,因为x并不是fn的局部变量和参数。而b是fn的局部参数,所以b是fn的约束变量。
那么现在我们可以解释一下闭包的第一个定义:
在计算机科学中,闭包是引用了自由变量的函数。
其实闭包不一定要是函数实例,也可以是代码块,只要满足可以保存变量在内存,同时有一些方法对于这些变量进行访问就行了。
所以,我们可以引申出闭包的第二个定义:
闭包是由函数和与其相关的引用环境组合而成的实例,环境由闭包创建时在作用域中的任何局部变量和参数组成。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
// 例子1
function Person(){
var name,
age;
function init(name, age){
name = name;
age = age;
}
function show(){
console.log('name: %s, age: %d', name, age);
}
return {
init: init,
show: show
}
}
var eyesiM = Person.init('EyesiM', 22); // 闭包的实例1
var dcc = Person.init('Dcc', 20); // 闭包的实例2
eyesiM.show(); // name: EyesiM, age: 22
dcc.show(); // name: Dcc, age: 20
上面的变量eyesiM和变量dcc就是闭包的实例,其中变量eyesiM的环境中局部变量name和age的值为'EyesiM'和22;变量dcc的环境中的局部变量name和age的值为'Dcc'和20。
二. 闭包的应用
闭包可以用来在一个函数与一组“私有”变量之间建立关联关系。在给定函数被多次调用的过程中,这些私有变量能够保存在内存中。变量的作用域仅限于包含它们的函数,因此无法从除包含它们的函数之外进行访问。
1. 模块模式
在Java等等一些语言里面会有private
关键字来将方法和变量声明为私有的,即它们只能被同一个类中的其它方法所调用。
JavaScript不提供原生的支持,但是可以使用闭包模拟私有变量和私有方法。私有变量可以限制对代码的访问;避免非核心的方法弄乱了代码的公共接口部分。
// 例子2
var demo = (function(){
// 模拟私有变量
var count = 0;
function show(){
console.log('count: %d', count++);
}
return {
show: show
}
})();
demo.show(); // 0
demo.count; // 我们不能直接引用,所以这里会返回undefined
在我理解,模块模式可以有两种用途:
立即调用函数表达式(IIFE):将我们自身的变量和方法封装起来,可以避免全局污染。例子2就是一个IIFE的例子。
引入依赖:我们可以引入对某一个全局对象的依赖,对这一个全局进行扩充。下面我们可以通过一个例子来表示。
// MODULE 就是一个全局对象,如果不存在就初始化为`{}`,我们使用局部参数my指向这个对象,接着我们给这个全局对象添加属性`method`,然后返回指向这个全局对象的引用。
var MODULE = (function (my) {
my.method = {
// add code
}
return my;
}(MODULE || {}));
2. 循环中创建闭包
在我们使用ES6的let关键字
之前,闭包的一个常见问题就出现在循环中创建闭包。
// 例子3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="apple">apple</div>
<div id="banana">banana</div>
<div id="orange">orange</div>
</body>
</html>
function showColor(item) {
console.log('id: %s, color: %s', item.id, item.color);
}
function addHTML() {
var colors = [{
id: 'apple',
color: 'red'
}, {
id: 'banana',
color: 'yellow'
}, {
id: 'orange',
color: 'orange'
}];
for (var i = 0, length = colors.length; i < length; i++) {
var item = colors[i];
// error
// 当我们把鼠标依次移过id为'apple', 'banana', 'orange'的div时,控制台打印出的是
// id: orange, color: orange
// id: orange, color: orange
// id: orange, color: orange
// document.getElementById(item.id).onmouseover = function(){
// showColor(item);
// }
// success
// 当我们把鼠标依次移过id为'apple', 'banana', 'orange'的div时,控制台打印出的是
// id: apple, color: red
// id: banana, color: yellow
// id: orange, color: orange
document.getElementById(item.id).onmouseover = function(item) {
return function() {
showColor(item);
};
}(item);
}
}
addHTML();
三. 闭包的注意点
闭包避免了环境中的变量被当成垃圾回收,因此使用闭包会使得闭包中的变量都被保存在内存中。
在一般的多页面中,我们关闭或重定向了页面之后,浏览器会自动回收原页面所占用的资源,但是如果我们所做的项目是SPA的话,就需要考虑到内存的使用,所以一定要慎用闭包。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。